package marco.android.CouchControl;

import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.FloatMath;

public class Particle
{
	float mX;
	float mY;
	float mZ;
	float mXo;
	float mYo;
	float mZo;
	float mDt;
	float mVel;
	float mVoX;
	float mVoY;
	float mVoZ;
	float mLifeTime;
	float [] G = {0f, 0f, 9.81f * 10};
	float mForce = 0.0f;
	float mForceX = 0;
	float mForceY = 0;
	float mForceZ = 0;
	float mXAccel = 0;
	float mYAccel = 0;
	float mZAccel = 0;
	float mXDot = 0;
	float mYDot = 0;
	float mZDot = 0;
	float mMass = 1;
	float mKE;
	float mV;
	float mBoundTop;
	float mBoundBottom;
	float mBoundLeft;
	float mBoundRight;
	float mStiffness = 1;
	int mAlpha = 0xFF;
	int mRed = 0xFF;
	int mBlue = 0xFF;
	int mGreen = 0xFF;
	int mColor;
	float mAlphaStep = 0;
	float mRedStep = 0;
	float mGreenStep = -30;
	float mBlueStep = -30;
	Paint mPaint = null;
	float mPDot = 0;
	float mQDot = 0;
	float mRDot = 0;
	float mP = 0;
	float mQ = 0;
	float mR = 0;
	float mPitch = 0;
	float mRoll = 0;
	float mYaw = 0;
	Bitmap mTexture = null; // The particle's texture
	
	//-------------------------------------------------------------------------
    //  Integration Scheme
    //  ------------------
    //  The following constant is used to select the integration
    //  scheme used for rates computation in this model.
    //  Some of the more common schemes follow:
    //
    //    0.0 Uses past accelerations    (lags too much)
    //    0.5 Uses average accelerations (slightly lags)
    //    1.0 Uses current accelerations (no lead/lag)
    //    1.5 Extrapolates accelerations (slightly leads)
    //    2.0 Extrapolates accelerations (leads too much
	float mIntegrationGain = 0.5f;
	float mPastXAccel = 0;
	float mPastYAccel = 0;
	float mPastZAccel = 0;
	float mDtFactor = 1.f;// Used to modify the effects of real-time
	
	public Particle(float startX, float startY,
							float boundT, float boundB, float boundL, float boundR,
							float dt)
	{
	    mXo = startX;
	    mYo = startY;
	    mDt = dt;
	    mBoundTop = boundT;
	    mBoundBottom = boundB;
	    mBoundLeft = boundL;
	    mBoundRight = boundR;
	    mLifeTime = (float)(Math.max(0.2, Math.random() * 1.0));
	    mGreenStep = mBlueStep = (255.f / -mLifeTime) * mDt;
	    mAlphaStep = 2.f * mGreenStep;
	    mStiffness = (float)(Math.max(0.01, Math.random() * 0.5));
	    mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setDither(true);
		mPaint.setStrokeCap(Paint.Cap.ROUND);
		mPaint.setStrokeWidth(8);
	    Initialize();
	}
	
	public void Initialize()
	{
		mColor = Color.argb(mAlpha, mRed, mGreen, mBlue);
		mPaint.setColor(mColor);
		double angle = Math.random() * (2.0 * Math.PI);
		mVel = (float)(Math.max(100.0, Math.random() * 400.0));
		mVoX = mVel * (float)Math.cos(angle);
		mVoY = mVel * (float)Math.sin(angle);
		
		mForce = (float)(Math.max(100 * G[2], Math.random() * 500 * (G[2])));
		mForceX = mForce * (float)Math.cos(angle);
		mForceY = mForce * (float)Math.sin(angle);
		
		mR = (float)(Math.max(0.0, Math.random() * 10.0 * Math.PI));
		mR = mR * (Math.random() > 0.5 ? 1.f : -1.f);
		mYaw = (float)angle;
		
		UpdateAccelerations();
		mX = mXo;
		mY = mYo;
	}
	
	public void SetTexture(Bitmap t)
	{
		mTexture = t;
	}
	
	private void CheckBoundCollision()
	{
		if(mX > mBoundRight || mX < mBoundLeft)
		{
			mForceX = -(mMass * (mXAccel + G[0]));// * mStiffness;
			mXDot = -mXDot * mStiffness;
			mX = Limit(mBoundLeft, mX, mBoundRight);
		}
		
		if(mY > mBoundBottom || mY < mBoundTop)
		{
			mForceY = -(mMass * (mYAccel + G[1]));
			mYDot = -mYDot * mStiffness;
			mY = Limit(mBoundTop, mY, mBoundBottom);
		}
	}
	
	private void UpdateAccelerations()
	{
		mPastXAccel = mXAccel;
		mPastYAccel = mYAccel;
		mPastZAccel = mZAccel;
		mXAccel = (mForceX / mMass) - G[0];
		mYAccel = ((mForceY / mMass) - G[1]);
		mZAccel = (mForceZ / mMass) - G[2];
		
		// These are instantaneous forces for now.
		mForceX = 0;
		mForceY = 0;
		mForceZ = 0;
	}
	
	private void UpdateRotationalVelocities()
	{
		mP += (mPDot * mDt);
		mQ += (mQDot* mDt);
		mR += (mRDot * mDt);
	}
	
	private void UpdateVelocities()
	{
		mXDot += (((mXAccel * mIntegrationGain) + (mPastXAccel * (1.f - mIntegrationGain))) * mDtFactor) * mDt;
		mYDot += (((mYAccel * mIntegrationGain) + (mPastYAccel * (1.f - mIntegrationGain))) * mDtFactor) * mDt;
		
		mV = FloatMath.sqrt((mXDot * mXDot) + (mYDot * mYDot));
	}
	
	private void UpdateRotationalPositions()
	{
		mRoll += (mP * mDt);
		mPitch += (mQ * mDt);
		mYaw += (mR * mDt);
	}
	
	private void UpdatePositions()
	{
		mX += (mXDot * mDt);
		mY += (mYDot * mDt);
//		mX = mXo + (mVoX * mT);
//		mY = mYo - (0.5f * G * (mT * mT)) + (mVoY * mT);
	}
	
	float [] mDefG = { 0f, 0f, -9.81f };
	public boolean Update()
	{
		return(Update(mDefG));
	}
	
	public boolean Update(float [] gravity)
	{
		G = gravity;
		UpdateAccelerations();
		UpdateRotationalVelocities();
		UpdateVelocities();
		UpdateRotationalPositions();
		UpdatePositions();
		
		CheckBoundCollision();
		
		// Update the transparency
		if(mGreen < 128)
		{
		   mAlpha += mAlphaStep;
		}
		
		// Update the colors
		mRed += mRedStep;
		mGreen += mGreenStep;
		mBlue += mBlueStep;
		mColor = Color.argb(mAlpha, mRed, mGreen, mBlue);
		mPaint.setColor(mColor);
		return((mLifeTime -= mDt) < 0.f);
	}
	
	public float GetX()
	{
		return(mX);
	}
	
	public float GetY()
	{
		return(mY);
	}
	
	public float GetVx()
	{
		return(mXDot);
	}
	
	public float GetVy()
	{
		return(mYDot);
	}
	
	public float GetBodyRotation()
	{
		return(mYaw * 180.f / (float)Math.PI);
	}
	
	public int GetColor()
	{
		return(mColor);
	}
	
	/**
	 * 
	 * @return A 4 item float array containing the RGBA color components
	 * of the particle in normalized float representation (0.f = black, 1.f = white)
	 */
	public float [] GetColorRGBA()
	{
		float [] c = { mRed / 255.f, mGreen / 255.f, mBlue / 255.f, mAlpha / 255.f };
		return(c);
	}
	
	public Paint GetPaint()
	{
		return(mPaint);
	}
	
	private float Limit(float low, float val, float high)
	{
		return(Math.max(low, Math.min(val, high)));
	}
}
